#include "thread.h"
#include "sem.h"

#include "mem.h"
#include "assert.h"

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <ucontext.h>
#include <signal.h>
#include <sys/time.h>

#define STACK_SIZE 16*1024

#define isempty(q) ((q) == NULL)

#define T Thread_T

struct T {
    ucontext_t ctx;
    T handle;
    // For user func
    int (*func)(void*);
    void *ud;
    // For queue
    T *inqueue;
    T link;
    T join;
    // For return code
    int code;
    // For release
    T next;
};

static struct T root;
static T current; 
static T ready;
static T join0;
static int nthreads;

static T freelist;

static int critical; // For preempting,  not interrupt critical section (kernal call)

// For a queue example:
//   put(t1, &ready) ready -> t1(ready)
//   put(t2, &ready) ready -> t1 -> t2(ready)
//   put(t3, &ready) ready -> t1 -> t2 -> t3(ready)
static void
put(T t, T *q)
{
    assert(t);
    assert(t->inqueue == NULL && t->link == NULL);
    if (*q) {
        t->link = (*q)->link;
        (*q)->link = t; // todo check
    } else {
        t->link = t;
    }
    *q = t;
    t->inqueue = q;
}

static T
get(T *q)
{
    assert(!isempty(*q));
    T t = (*q)->link;
    if (t == *q)
        *q = NULL;
    else
        (*q)->link = t->link;
    assert(t->inqueue == q);
    t->link = NULL;
    t->inqueue = NULL;
    return t;
}

static void
delete(T t, T *q)
{
    assert(t->link && t->inqueue == q);
    assert(!isempty(q));
    T p;
    for (p = *q; p->link != t; p = p->link)
        ;
    if (p == t) {
        *q = NULL;
    } else {
        p->link = t->link;
        if (*q == t)
            *q = p;
    }
    t->link = NULL;
    t->inqueue = NULL;
}

static void
run()
{
    T t = current;
    current = get(&ready);
    if (swapcontext(&t->ctx, &current->ctx) < 0) {
        printf("swap error:%d\n", errno);
    }
}

static void
release()
{
    T t;
    do { critical++;
        while ((t = freelist) != NULL) {
            freelist = t->next;
            FREE(t);
        }
    critical--; } while (0);
}

static void
interrupt(int code)
{
    // printf("interrupt current now:%p - %d\n", (void*)current, critical);
    if (critical)
        return;
    (void)code;
    put(current, &ready);
    run();
}

// 关于抢占模式 preempt=1 说明：
//   抢占模式使用定时器 ITIMER_REAL 触发 SIGALRM 信号触发信号处理程序，在处理程序中切换线程，一开始使用 ITIMER_VIRTUAL 但会导致进程卡住，会找到原因 .
//   使用 ITIMER_REAL 后进程不再卡住，但发现不能切换线程，且会不时的 core dump .
//   由于使用信号，会导致在用户函数中的调用的内核函数会由于中断返回，比如 usleep(1000*1000) 睡眠 1 秒，但函数会在信号处理程序返回后返回，破坏了用户调用 usleep 的意图 .
//   因此，放弃抢占模式，用户主动调用 Thread_pause 放弃对 CPU 的占用 .
int Thread_init(int preempt)
{
    assert(preempt == 0);
    assert(current == NULL && nthreads == 0);
    root.handle = &root;
    current = &root;

    nthreads++;

    if (preempt) {
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = interrupt;
        if (sigaction(SIGALRM, &sa, NULL) < 0)
            return 0; // Not support preempt

        struct itimerval it;
        it.it_value.tv_sec = 0;
        it.it_value.tv_usec = 10;
        it.it_interval.tv_sec = 0;
        it.it_interval.tv_usec = 10;
        if (setitimer(ITIMER_REAL, &it, NULL) < 0)
            return 0;
    }

    // Todo: 用于书中的 Thread_alert 函数，用于从队列中删除被标记为警告状态的线程，暂时不需要 .
    (void)delete;
    return 1;
}

static void
mainfunc(uint32_t lo32, uint32_t hi32)
{
    T t = (T)((uintptr_t)lo32 | ((uintptr_t)hi32 << 32));
    int code = t->func(t->ud);
    Thread_exit(code);
}

T Thread_new(int apply(void*), void *arg, int nbytes)
{
    assert(apply);
    assert((arg && nbytes >= 0) || (arg == NULL && nbytes == 0));

    release();

    T t;
    int total = (STACK_SIZE + nbytes + sizeof(*t) + 15) & ~15;
    do { critical++;
        t = (T)ALLOC(total);
        memset(t, 0, total);
    critical--; } while (0);

    t->handle = t;
    t->func = apply;
    t->ud = arg;
    if (nbytes > 0) {
        do { critical++;
            t->ud = (char*)t + sizeof(*t);
            memcpy(t->ud, arg, nbytes);
        critical--; } while (0);
    }

    do { critical++;
        getcontext(&t->ctx);
        t->ctx.uc_stack.ss_sp = (char*)t + sizeof(*t) + nbytes;
        t->ctx.uc_stack.ss_size = STACK_SIZE;
        // t->ctx.uc_link = &root.ctx; todo

        uintptr_t ptr = (uintptr_t)(t);
        makecontext(&t->ctx, (void (*)(void))mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));
    critical--; } while (0);

    nthreads++;
    put(t, &ready); // Not run now
    return t;
}

void Thread_exit(int code)
{
    assert(current);

    release();
    // Delay releasing current thread
    if (current != &root) {
        current->next = freelist;
        freelist = current;
    }

    current->handle = NULL;
    while (!isempty(current->join)) {
        T t = get(&current->join);
        t->code = code;
        put(t, &ready);
    }

    if (!isempty(join0) && nthreads == 2) {
        assert(isempty(ready));
        put(get(&join0), &ready);
    }

    if (--nthreads == 0)
        exit(code);
    else
        run();
}

T Thread_self()
{
    assert(current);
    return current;
}

int Thread_join(T t)
{
    assert(current && t != current);
    if (t) {
        if (t->handle == t) {
            put(current, &t->join);
            run();
            return current->code;
        } else {
            return -1;
        }
    } else {
        assert(isempty(join0));
        if (nthreads > 1) {
            put(current, &join0);
            run();
        }
        return 0;
    }
}

void Thread_pause()
{
    assert(current);
    put(current, &ready);
    run();
}

#undef T


#define T Sem_T

void Sem_init(T *s, int count)
{
    assert(current);
    assert(s);

    s->count = count;
    s->queue = NULL;
}

T* Sem_new(int count)
{
    T *s;
    NEW(s);
    Sem_init(s, count);
    return s;
}

// 看一下书中关于为何这样实现 wait 和 signal 的说明 .
// 主要要避免某线程在 ready 和 s->queue 队列之间无限循环，造成线程饥饿 .
// 如果通常实现 signal 加 1 且把线程 A 放入到 ready 队列，而 wait 减 1 把线程 B 放入到 s->queue 队列，此时不能保证 A 和 B 是同一个线程，于是产生了不公平，也就出现了饥饿 .
void Sem_wait(T *s)
{
    assert(current);
    assert(s);

    // 当线程从 wait 中恢复执行时，也不对 s->count 执行减 1 .
    if (s->count <= 0) {
        put(current, (Thread_T*)&s->queue);
        run();
    } else {
        --s->count;
    }
}

void Sem_signal(T *s)
{
    assert(current);
    assert(s);

    // 在 s->count 从 0 变 1 时，将一个线程 s->queue 移动到 ready 但并不对 s->count 加 1 .
    // 这样，此线程一定获得了信号量 .
    if (s->count == 0 && !isempty(s->queue)) {
        Thread_T t = get((Thread_T*)&s->queue);
        put(t, &ready);
    } else {
        ++s->count;
    }
}

#undef T

